﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using TwainDotNet.Win32;

namespace TwainDotNet.TwainNative
{
    /// <summary>
    /// /* DAT_CAPABILITY. Used by application to get/set capability from/in a data source. */
    /// typedef struct {
    ///    TW_UINT16  Cap; /* id of capability to set or get, e.g. CAP_BRIGHTNESS */
    ///    TW_UINT16  ConType; /* TWON_ONEVALUE, _RANGE, _ENUMERATION or _ARRAY   */
    ///    TW_HANDLE  hContainer; /* Handle to container of type Dat              */
    /// } TW_CAPABILITY, FAR * pTW_CAPABILITY;
    /// </summary>
    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    public class TwainCapability : IDisposable
    {
        Capabilities _capabilities;
        ContainerType _containerType;
        TwainHandle _handle;
        object _value;

        protected TwainCapability(Capabilities capabilities, ContainerType containerType, object value)
        {
            _capabilities = capabilities;
            _containerType = containerType;
            _value = value;
            if (value != null&&!value.Equals(0))
            {
                _handle.Alloc(Marshal.SizeOf(value));

                _handle.Lock();
                try
                {
                    switch (containerType)
                    {
                        case ContainerType.One:
                            this._handle.WriteUInt16((UInt16)((CapabilityOneValue)value).TwainTyp);
                            SetValue(((CapabilityOneValue)value).Value, ((CapabilityOneValue)value).TwainTyp);
                            break;
                       /* case ContainerType.Range:
                            this._handle.WriteRange(((CapabilityRangeValue)value));
                            break;
                        case ContainerType.Array:
                            this._handle.WriteUInt16((UInt16)(((CapabilityArrayValue)value).TwainTyp));
                            this._handle.WriteUInt32((UInt32)((CapabilityArrayValue)value).NumItems);
                            //((CapabilityArrayValue)value).Values
                            break;*/
                        default:
                            throw new NotSupportedException("Unsupported container type");
                    }


                }
                finally
                {
                    _handle.Unlock();
                }
            }


        }

        ~TwainCapability()
        {
            Dispose(false);
        }

        private void SetValue(object value, TwainType valuetype)
        {
             switch (valuetype)
            {
                case TwainType.Fix32:
                    float flv = (float)Convert.ToDouble(value); 
                    Fix32 fv = new Fix32(flv);
                    this._handle.WriteFix32(fv);
                    break;
                case TwainType.Int8:
                    this._handle.WriteInt8(Convert.ToSByte(value));
                    break;
                case TwainType.UInt8:
                    this._handle.WriteUInt8(Convert.ToByte(value));
                    break;
                case TwainType.Int16:
                    this._handle.WriteInt16(Convert.ToInt16(value));
                    break;
                case TwainType.UInt16:
                    this._handle.WriteUInt16(Convert.ToUInt16(value));
                    break;
                case TwainType.Int32:
                    this._handle.WriteInt32(Convert.ToInt32(value));
                    break;
                case TwainType.UInt32:
                    this._handle.WriteUInt32(Convert.ToUInt32(value));
                    break;
                case TwainType.Str32:
                    this._handle.WriteString((String)value, ((String)value).ToCharArray().Length);
                    break;
                case TwainType.Str64:
                    this._handle.WriteString((String)value, ((String)value).ToCharArray().Length);
                    break;
                case TwainType.Str128:
                    this._handle.WriteString((String)value, ((String)value).ToCharArray().Length);
                    break;
                case TwainType.Str255:
                    this._handle.WriteString((String)value, ((String)value).ToCharArray().Length);
                    break;
                case TwainType.Str512:
                    this._handle.WriteString((String)value, ((String)value).ToCharArray().Length);
                    break;
                case TwainType.Str1024:
                    this._handle.WriteString((String)value, ((String)value).ToCharArray().Length);
                    break;
                case TwainType.Bool:
                    this._handle.WriteUInt16(Convert.ToUInt16(value));
                    break;
                default:
                    break;
            }
        }       

        private object GetValue(TwainType valuetype)
        {
            object ret;
            switch (valuetype)
            {
                case TwainType.Fix32:
                    ret = (object)(this._handle.ReadFix32());
                    break;
                case TwainType.Int8:
                    ret = (object)(this._handle.ReadInt8());
                    break;
                case TwainType.UInt8:
                    ret = (object)(this._handle.ReadUInt8());
                    break;
                case TwainType.Int16:
                    ret = (object)(this._handle.ReadInt16());
                    break;
                case TwainType.UInt16:
                    ret = (object)(this._handle.ReadUInt16());
                    break;
                case TwainType.Int32:
                    ret = (object)(this._handle.ReadInt32());
                    break;
                case TwainType.UInt32:
                    ret = (object)(this._handle.ReadUInt32());
                    break;
                case TwainType.Str32:
                    ret = (object)(this._handle.ReadString());
                    break;
                case TwainType.Str64:
                    ret = (object)(this._handle.ReadString());
                    break;
                case TwainType.Str128:
                    ret = (object)(this._handle.ReadString());
                    break;
                case TwainType.Str255:
                    ret = (object)(this._handle.ReadString());
                    break;
                case TwainType.Str512:
                    ret = (object)(this._handle.ReadString());
                    break;
                case TwainType.Str1024:
                    ret = (object)(this._handle.ReadString());
                    break;
                case TwainType.Bool:
                    ret = (object)(this._handle.ReadUInt16());
                    break;
                default:
                    ret = null;
                    break;
            }
            return ret;
        }


        public void ReadBackValue()
        {
            if (this._handle.Lock())
            {
                switch (this._containerType)
                {
                    case ContainerType.Array:
                        CapabilityArrayValue myarray = new CapabilityArrayValue();
                        myarray.TwainTyp = (TwainType)this._handle.ReadUInt16();
                        myarray.NumItems = this._handle.ReadUInt32();
                        for (int i = 1; i <= myarray.NumItems; i++)
                        {
                            myarray.Values.Add(GetValue(myarray.TwainTyp));
                        }
                        this._value = (object)myarray;
                        break;
                    case ContainerType.Enum:
                        CapabilityEnumValue myenum = new CapabilityEnumValue();
                        myenum.TwainTyp = (TwainType)this._handle.ReadUInt16();
                        myenum.ItemCount = this._handle.ReadUInt32();
                        myenum.CurrentIndex = this._handle.ReadUInt32();
                        myenum.DefaultIndex = this._handle.ReadUInt32();
                        for (int i = 1; i <= myenum.ItemCount; i++)
                        {
                            myenum.Values.Add(GetValue(myenum.TwainTyp));
                        }
                        this._value = (object)myenum;
                        break;
                    case ContainerType.Range:
                        CapabilityRangeValue myrange = this._handle.ReadRange();
                        this._value = (object)myrange;
                        break;
                    case ContainerType.One:
                        CapabilityOneValue myone = new CapabilityOneValue();
                        myone.TwainTyp = (TwainType)this._handle.ReadUInt16();
                        myone.Value = GetValue(myone.TwainTyp);
                        this._value = (object)myone;
                        break;
                    default:
                        throw new TwainException("Unknown Capability Container Type");
                }
                this._handle.Unlock();
            }
        }

        public object ReadedValue
        {
            get 
            {
                return _value;
            }
        }

        public static TwainCapability From<TValue>(Capabilities capabilities, TValue value)
        {
            ContainerType containerType;
            Type structType = typeof(TValue);
            if (structType == typeof(CapabilityOneValue))
            {
                containerType = ContainerType.One;
            }
            else if (structType== typeof(CapabilityArrayValue))
            {
                containerType = ContainerType.Array;
            }
            else if (structType == typeof(CapabilityEnumValue))
            {
                containerType = ContainerType.Enum;
            }
            else if (structType == typeof(CapabilityRangeValue))
            {
                containerType = ContainerType.Range;
            }
            else
            {
                containerType = ContainerType.DontCare;
                //throw new NotSupportedException("Unsupported type: " + structType);
            }

            return new TwainCapability(capabilities, containerType, value);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_handle != IntPtr.Zero)
            {
                _handle.Free();
            }
        }
    }
}
